// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.repo

import android.content.Context
import android.util.Log
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.api.structs.FeedInfoV1
import xyz.apiote.bimba.czwek.api.structs.FeedInfoV2
import xyz.apiote.bimba.czwek.api.structs.QrLocationV1
import xyz.apiote.bimba.czwek.data.FeedInfoFlowItem
import xyz.apiote.fruchtfleisch.Reader
import xyz.apiote.fruchtfleisch.Writer
import java.io.InputStream
import java.io.OutputStream
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale

data class FeedInfoPrev(
	val id: String,
	val name: String,
	val attribution: String,
	val description: String,
	val lastUpdate: LocalDate,
	val qrHost: String,
	val qrIn: QrLocation,
	val qrSelector: String,
	val validSince: LocalDate?,
	val validTill: LocalDate?,
	val cached: Boolean
) {
	companion object {
		private fun parseDate(dateString: String) =
			LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE)

		fun unmarshal(stream: InputStream): FeedInfo {
			val reader = Reader(stream)
			val id = reader.readString()
			val name = reader.readString()
			val attribution = reader.readString()
			val description = reader.readString()
			val lastUpdate = parseDate(reader.readString())
			val qrHost = reader.readString()
			val qrIn = QrLocation.of(QrLocationV1.of(reader.readUInt().toULong().toUInt()))
			val qrSelector = reader.readString()
			val validSince = reader.readString()
			val validTill = reader.readString()

			return FeedInfo(
				FeedInfoPrev(
					id,
					name,
					attribution,
					description,
					lastUpdate,
					qrHost,
					qrIn,
					qrSelector,
					if (validSince != "") parseDate(validSince) else null,
					if (validTill != "") parseDate(validTill) else null,
					true
				)
			)
		}
	}
}

data class FeedInfo(
	val id: String,
	val name: String,
	val attribution: String,
	val description: String,
	val lastUpdate: LocalDate,
	val qrHost: String,
	val qrIn: QrLocation,
	val qrSelector: String,
	val validSince: LocalDate?,
	val validTill: LocalDate?,
	val cached: Boolean,
	val isOnline: Boolean,
	val isOffline: Boolean
): FeedInfoFlowItem {
	companion object {
		const val VERSION = 101u
		const val ID_TRANSITOUS = "transitous"
		const val ID_GEOCODING = "geocoding"

		fun getTransitous(context: Context) = FeedInfo(
			ID_TRANSITOUS,
			context.getString(R.string.transitous),
			context.getString(R.string.transitous_description),
			context.getString(R.string.transitous_attribution),
			LocalDate.now(),
			"",
			QrLocation.NONE,
			"",
			null,
			null,
			cached=false,
			isOnline=true,
			isOffline=false
		)
		
		fun getGeocoding(context: Context) = FeedInfo(
			ID_GEOCODING,
			context.getString(R.string.geocoding),
			context.getString(R.string.geocoding_description),
			context.getString(R.string.geocoding_attribution),
			LocalDate.now(),
			"",
			QrLocation.NONE,
			"",
			null,
			null,
			cached=false,
			isOnline=true,
			isOffline=true
		)

		private fun parseDate(dateString: String) =
			LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE)


		fun unmarshal(stream: InputStream): FeedInfo {
			val reader = Reader(stream)
			val id = reader.readString()
			val name = reader.readString()
			val attribution = reader.readString()
			val description = reader.readString()
			val lastUpdate = parseDate(reader.readString())
			val qrHost = reader.readString()
			val qrIn = QrLocation.of(QrLocationV1.of(reader.readUInt().toULong().toUInt()))
			val qrSelector = reader.readString()
			val validSince = reader.readString()
			val validTill = reader.readString()
			val isOnline = reader.readString() == "true"
			val isOffline = reader.readString() == "true"

			return FeedInfo(
				id,
				name,
				attribution,
				description,
				lastUpdate,
				qrHost,
				qrIn,
				qrSelector,
				if (validSince != "") parseDate(validSince) else null,
				if (validTill != "") parseDate(validTill) else null,
				true,
				isOnline,
				isOffline
			)
		}
	}

	constructor(f: FeedInfoV2, cached: Boolean = false) : this(
		f.id,
		f.name,
		f.attribution,
		f.description,
		f.lastUpdate,
		f.qrHost,
		QrLocation.of(f.qrIn),
		f.qrSelector,
		f.validSince,
		f.validTill,
		cached,
		true,
		false
	)

	constructor(f: FeedInfoV1, cached: Boolean = false) : this(
		f.id,
		f.name,
		f.attribution,
		f.description,
		f.lastUpdate.toLocalDate(),
		"",
		QrLocation.UNKNOWN,
		"",
		null,
		null,
		cached,
		true,
		false
	)

	constructor(f: FeedInfoPrev) : this(
		f.id,
		f.name,
		f.attribution,
		f.description,
		f.lastUpdate,
		f.qrHost,
		f.qrIn,
		f.qrSelector,
		f.validSince,
		f.validTill,
		true,
		true,
		false
	)

	fun marshal(stream: OutputStream) {
		val writer = Writer(stream)
		writer.writeString(id)
		writer.writeString(name)
		writer.writeString(attribution)
		writer.writeString(description)
		writer.writeString(formatDateMarshal(lastUpdate))
		writer.writeString(qrHost)
		writer.writeUInt(qrIn.value().toULong())
		writer.writeString(qrSelector)
		writer.writeString(if (validSince == null) "" else formatDateMarshal(validSince))
		writer.writeString(if (validTill == null) "" else formatDateMarshal(validTill))
		writer.writeString(if (isOnline) "true" else "false")
		writer.writeString(if (isOffline) "true" else "false")
	}

	private fun formatDateMarshal(date: LocalDate): String {
		return date.format(DateTimeFormatter.ISO_LOCAL_DATE)
	}

	fun formatDate(): String {
		return lastUpdate.format(
			DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.getDefault())
		)
	}

	fun sortGroup(): String {
		return when (id) {
			ID_GEOCODING -> "0"
			ID_TRANSITOUS -> "0"
			else -> "1"
		}
	}
}

fun joinFeeds(
	feeds1: Map<String, FeedInfo>?,
	feeds2: Map<String, FeedInfo>?
): Map<String, FeedInfo> {
	Log.i("joinFeeds", "f1: $feeds1, f2: $feeds2")
	if (feeds1.isNullOrEmpty() && feeds2.isNullOrEmpty()) {
		return emptyMap()
	}

	if (feeds1.isNullOrEmpty()) {
		return feeds2!!
	}
	if (feeds2.isNullOrEmpty()) {
		return feeds1
	}

	return feeds1.keys.union(feeds2.keys).associateWith {
		feeds1[it].join(feeds2[it])
	}
}

fun FeedInfo?.join(other: FeedInfo?): FeedInfo {
	assert(this != null || other != null)

	if (this == null) return other!!
	if (other == null) return this

	return FeedInfo(
		id,
		other.name,
		other.attribution,
		other.description,
		if (lastUpdate.isAfter(other.lastUpdate)) lastUpdate else other.lastUpdate,
		other.qrHost,
		other.qrIn,
		other.qrSelector,
		if (other.validSince == null || (validSince
				?: LocalDate.MIN).isAfter(other.validSince)
		) validSince else other.validSince,
		if (other.validTill == null || (validTill
				?: LocalDate.MIN).isAfter(other.validTill)
		) validTill else other.validTill,
		this.cached && other.cached,
		this.isOnline && other.isOnline,
		this.isOffline && other.isOffline
	)
}
